﻿using System;
using System.Linq;
using FakeItEasy;
using NUnit.Framework;

namespace NJabber.Client
{
    [TestFixture]
    public class PresenceSubscriptionRegistryTest
    {
        private IPresenceSubscriptionResponder responder;
        private PresenceSubscriptionRegistry registry;
        private JID requestJid = "some@jid.com";

        [SetUp]
        public void SetUp()
        {
            responder = A.Fake<IPresenceSubscriptionResponder>();
            registry = new PresenceSubscriptionRegistry(responder);
        }

        [Test]
        [ExpectedException(typeof (ArgumentNullException))]
        public void Constructor_NullParameter_ThrowsException()
        {
            PresenceSubscriptionRegistry registry = new PresenceSubscriptionRegistry(null);
        }



        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void InboundRequest_NullParameter_ThrowsException()
        {
            PresenceSubscriptionRequest request = registry.InboundRequest(null);
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void InboundRequest_NotExistedRequestJid_ThrowsException()
        {
            PresenceSubscriptionRequest request = registry.InboundRequest("not-existed@jid.org");
        }

        [Test]
        public void InboundRequest_RegistryContainsRequest_ReturnsRequest()
        {
            registry.RegisterInboundRequest(requestJid);

            PresenceSubscriptionRequest request = registry.InboundRequest(requestJid);

            Assert.That(request, Is.Not.Null);
            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        public void InboundRequest_RegisteringTwoIdenticalRequests_CorrectlyReturnsRequest()
        {
            registry.RegisterInboundRequest(requestJid);
            registry.RegisterInboundRequest(requestJid);

            PresenceSubscriptionRequest request = registry.InboundRequest(requestJid);

            Assert.That(request, Is.Not.Null);
            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void OutboundRequest_NullParameter_ThrowsException()
        {
            PresenceSubscriptionRequest request = registry.OutboundRequest(null);
        }

        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void OutboundRequest_NotExistedRequestJid_ThrowsException()
        {
            PresenceSubscriptionRequest request = registry.OutboundRequest("not-existed@jid.org");
        }

        [Test]
        public void OutboundRequest_RegistryContainsRequest_ReturnsRequest()
        {
            registry.RegisterOutboundRequest(requestJid);

            PresenceSubscriptionRequest request = registry.OutboundRequest(requestJid);

            Assert.That(request, Is.Not.Null);
            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        public void OutboundRequest_RegisteringTwoIdenticalRequests_ReturnsRequest()
        {
            registry.RegisterOutboundRequest(requestJid);
            registry.RegisterOutboundRequest(requestJid);

            PresenceSubscriptionRequest request = registry.OutboundRequest(requestJid);

            Assert.That(request, Is.Not.Null);
            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        public void RegisterInboundRequest_AnyJid_InboundRequestsContainRequestWithGivenJid()
        {
            registry.RegisterInboundRequest(requestJid);
            PresenceSubscriptionRequest request = registry.InboundRequests.Single(r => r.EntityJid == requestJid);

            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void RegisterInboundRequest_NullParameter_ThrowsException()
        {
            registry.RegisterInboundRequest(null);
        }

        [Test]
        public void RegisterOutboundRequest_AnyJid_OutboundRequestsContainsRequestWithGivenJid()
        {
            registry.RegisterOutboundRequest(requestJid);
            PresenceSubscriptionRequest request = registry.OutboundRequests.Single(r => r.EntityJid == requestJid);

            Assert.That(request.EntityJid, Is.EqualTo(requestJid));
        }

        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void RegisterOutboundRequest_NullParameter_ThrowsException()
        {
            registry.RegisterOutboundRequest(null);
        }

        [Test]
        public void RequestApprove_InboundRequest_SendsApprovementResponse()
        {
            registry.RegisterInboundRequest(requestJid);
            registry.InboundRequest(requestJid).Approve();

            A.CallTo(() => responder.SendApproveResponse(requestJid)).MustHaveHappened();
        }

        [Test]
        public void RequestDeny_InboundRequest_SendsDenyResponse()
        {
            registry.RegisterInboundRequest(requestJid);
            registry.InboundRequest(requestJid).Deny();

            A.CallTo(() => responder.SendDenyResponse(requestJid));
        }

        [Test]
        public void RequestApprove_InboundRequest_RemovesRequestAndRisesEvent()
        {
            VerifyRequestAction(requestJid, true, true);
        }

        [Test]
        public void RequestApprove_OutboundRequest_RemovesRequestAndRisesEvent()
        {
            VerifyRequestAction(requestJid, false, true);
        }

        [Test]
        public void RequestDeny_InboundRequest_RemovesRequestAndRisesEvent()
        {
            VerifyRequestAction(requestJid, true, false);
        }

        [Test]
        public void RequestDeny_OutboundRequest_RemovesRequestAndRisesEvent()
        {
            VerifyRequestAction(requestJid, false, false);
        }

        private void VerifyRequestAction(JID requestJid, bool isInboundRequest, bool isApprove)
        {
            RegisterRequest(requestJid, isInboundRequest);
            EventHandler<SubscriptionRequestEventArgs> handler = A.Fake<EventHandler<SubscriptionRequestEventArgs>>();
            AttachHandler(isInboundRequest, isApprove, handler);
            PresenceSubscriptionRequest request = GetRequest(requestJid, isInboundRequest);

            PerformRequestAction(request, isApprove);

            SubscriptionRequestEventArgs expectedArgs = new SubscriptionRequestEventArgs(request);
            A.CallTo(() => handler.Invoke(registry, expectedArgs)).MustHaveHappened();
            CollectionAssert.DoesNotContain(isInboundRequest ? registry.InboundRequests : registry.OutboundRequests, request);
        }

        private void RegisterRequest(JID requestJid, bool isInboundRequest)
        {
            if (isInboundRequest) registry.RegisterInboundRequest(requestJid);
            else registry.RegisterOutboundRequest(requestJid);
        }

        private void AttachHandler(bool isInboundRequest, bool isApprove, EventHandler<SubscriptionRequestEventArgs> handler)
        {
            if (isInboundRequest && isApprove) registry.InboundRequestApproved += handler;
            if (isInboundRequest && !isApprove) registry.InboundRequestDenied += handler;
            if (!isInboundRequest && isApprove) registry.OutboundRequestApproved += handler;
            if (!isInboundRequest && !isApprove) registry.OutboundRequestDenied += handler;
        }

        private PresenceSubscriptionRequest GetRequest(JID requestJid, bool isInboundRequest)
        {
            if(isInboundRequest)return registry.InboundRequest(requestJid);
            return registry.OutboundRequest(requestJid);
        }

        private void PerformRequestAction(PresenceSubscriptionRequest request, bool isApprove)
        {
            if (isApprove) request.Approve();
            else request.Deny();
        }
    }
}
